/*
 * ObservadorUniverso.cpp
 *
 *  Created on: Apr 19, 2010
 *      Author: jgrande
 */

#include "ObservadorUniverso.h"

using aplicacion::t_listaPlanetas;
using aplicacion::RegistrySingleton;
using aplicacion::MensajeAplicacion;
using aplicacion::DatosCrearFlotaAvisoJugador;
using aplicacion::DatosCambiarRumboAvisoJugador;
using aplicacion::DatosCambiaDuenio;
using aplicacion::DatosCantidadNaves;
using aplicacion::DatosArriboNave;
using aplicacion::DatosFinJuego;
using aplicacion::jugador::Jugador;
using aplicacion::jugador::Juego;
using aplicacion::jugador::Flota;

using middleware::comunicaciones::TraductorDireccionesSingleton;

static MapaFlotas* gFlotas;
static Jugador* gJugador;
static t_listaPlanetas gPlanetas;
static bool iniciado = false;

bool actualizarDuenioPlaneta(IdPlaneta idPlaneta, IdJugador duenioNuevo) {
	t_listaPlanetas::iterator it;
	for(it=gPlanetas.begin();it!=gPlanetas.end();it++) {
		if(it->_identificador==idPlaneta) {
			it->_duenio = duenioNuevo;
			return true;
		}
	}
}

bool juegoTerminado() {
	if(gPlanetas.size()==1 && gPlanetas.front()._duenio!=IdJugador::ID_UNDEFINED) {
		return true;
	} else if(gPlanetas.size()>1) {
		IdJugador primero = IdJugador::ID_UNDEFINED;
		t_listaPlanetas::iterator it;

		// busco algún planeta cuyo dueño no sea el jugador neutro
		// y guardo el id del jugador en ``primero''
		for(it=gPlanetas.begin();it!=gPlanetas.end();it++) {
			if(it->_duenio!=IdJugador::ID_UNDEFINED) {
				primero = it->_duenio;
				break;
			}
		}

		// si no encontré ningún planeta con dueño, el juego no terminó,
		// probablemente recién empieza
		if(primero==IdJugador::ID_UNDEFINED) {
			return false;
		}

		// si hay algún planeta con dueño distinto a ``primero'', el
		// juego no terminó
		for(it=gPlanetas.begin(); it!=gPlanetas.end(); it++) {
			if(it->_duenio!=primero) {
				return false;
			}
		}

		// si ``primero'' es dueño de todos los planetas, el juego terminó
		return true;
	}
}

bool perdioJugadorLocal() {
	gFlotas->lock();
	if(gFlotas->size()!=0) {
		gFlotas->unlock();
		return false;
	}
	gFlotas->unlock();

	t_listaPlanetas::iterator it = gPlanetas.begin();
	for(;it!=gPlanetas.end();it++) {
		if(it->_duenio==gJugador->getDTO().idJugador) {
			return false;
		}
	}

	return true;
}

void informarGanadorAPlanetas() {
	DatosFinJuego* datos = new DatosFinJuego();
	datos->_ganador = gJugador->getDTO().idJugador;
	MensajeAplicacion msj;
	msj.getEncabezado().setOrigen(gJugador->getDTO().idJugador);
	msj.getEncabezado().setTipo(FIN_JUEGO);
	msj.setDatos(datos);

	t_listaPlanetas::iterator it;
	for(it=gPlanetas.begin();it!=gPlanetas.end();it++) {
		logger << LogType::LOG_DEBUG_1 << "Informando el final del juego al "
				"planeta " << logger.underline << it->_identificador << 
				logger.nounderline << logger.endl;
		Direccion dest = TraductorDireccionesSingleton::instance().getDireccion(it->_identificador);
		if(dest==DIR_INVALIDA) {
			logger << LogType::LOG_ERROR << "Error: no se encontró la dirección "
					"del planeta " << it->_identificador << logger.endl;
		} else {
			msj.getEncabezado().setDestino(it->_identificador);
			if(!MiddlewareClienteSingleton::instance().send(msj, dest)) {
				logger << LogType::LOG_ERROR << "Error: no se pudo enviar "
						"mensaje al planeta " << it->_identificador << logger.endl;
			}
		}
	}
}

void informarJuegoTerminado() {
	if(gPlanetas.size()==0) {
		return;
	}

	IdJugador ganador = gPlanetas.front()._duenio;

	if(ganador==gJugador->getDTO().idJugador) {
		logger << "Felicitaciones! Ganaste el juego!" << logger.endl;
		informarGanadorAPlanetas();
	} else {
		logger << "Terminó el juego! El ganador es el jugador " <<
				logger.underline << ganador << 
				logger.nounderline << logger.endl;
	}
}

void informarPerdioJugadorLocal() {
	logger << "Perdiste! Ya no te quedan más flotas ni planetas!"
			<< logger.endl;
}

void onFlotaCreada(DatosCrearFlotaAvisoJugador& msj) {
	logger << "Se creó la flota " << logger.underline << msj._idFlota <<
			logger.nounderline << " con destino " << logger.underline <<
			msj._idDestino << logger.nounderline << " y " << 
			msj._cantidadNaves << " naves." << logger.endl;

	if(msj._idFlota.getIdJugador()==gJugador->getDTO().idJugador) {
		Flota f(msj._idFlota, msj._idDestino, msj._cantidadNaves);
		gFlotas->lock();
		if(!gFlotas->put(msj._idFlota.getId(), f)) {
			logger << LogType::LOG_ERROR << "Error! No se pudo registrar la flota porque el mapa de"
					<< " flotas está lleno." << logger.endl;
		} else {
			logger << "Se recibió el aviso de creación de la flota local "
					<< logger.underline << msj._idFlota 
					<< logger.nounderline << logger.endl;
		}
		gFlotas->unlock();
	}
}

void onCambioRumboFlota(DatosCambiarRumboAvisoJugador& msj) {
	logger << "Se cambió el rumbo de la flota " << logger.underline <<
			msj._idFlota << logger.nounderline <<
			", con nuevo destino " << logger.underline <<
			msj._idNuevoDestino << logger.nounderline << logger.endl;

	if(msj._idFlota.getIdJugador()==gJugador->getDTO().idJugador) {
		gFlotas->lock();
		if(!gFlotas->contains(msj._idFlota.getId())) {
			logger << LogType::LOG_WARNING <<
					"Advertencia! Se hayó una inconsistencia. La flota " <<
					logger.underline << msj._idFlota << logger.nounderline << 
					" debería ser de este jugador, pero " <<
					"no se la encontró en la tabla de flotas" << logger.endl;
		} else {
			Flota f = gFlotas->get(msj._idFlota.getId());
			f.setDestino(msj._idNuevoDestino);
			if(!gFlotas->put(msj._idFlota.getId(), f)) {
				logger << LogType::LOG_ERROR <<
						"Error! No se pudo actualizar el destino de la " <<
						"flota porque el mapa de flotas está lleno." <<
						logger.endl;
			} else {
				logger << "Se recibió el aviso de cambio de rumbo de la flota "
						"local " << logger.underline << msj._idFlota << 
						logger.nounderline << logger.endl;
			}
		}
		gFlotas->unlock();
	}
}

void onCambioDuenio(IdPlaneta idPlaneta, DatosCambiaDuenio& msj) {
	if(msj._duenioAnterior==gJugador->getDTO().idJugador) {
		logger << "Perdiste el planeta " << logger.underline <<
			idPlaneta << logger.nounderline << logger.endl;
	} else if(msj._duenioNuevo==gJugador->getDTO().idJugador) {
		logger << "Conquistaste el planeta " << logger.underline <<
			idPlaneta << logger.nounderline << logger.endl;
		iniciado = true;
	} else {
		logger << "El planeta " << logger.underline << idPlaneta <<
				logger.nounderline << " fue conquistado por el "
				"jugador " << logger.underline <<
				msj._duenioNuevo << logger.nounderline << 
				logger.endl;
	}

	if(!actualizarDuenioPlaneta(idPlaneta, msj._duenioNuevo)) {
		logger << LogType::LOG_ERROR << "Error actualizando dueño: no se "
				"encontró el planeta " << idPlaneta;
	}
}

void onCantNaves(IdPlaneta idPlaneta, DatosCantidadNaves& msj) {
	logger << "El planeta " << logger.underline << idPlaneta << 
			logger.nounderline << " tiene " <<
			msj._cantidadNaves << " naves." << logger.endl;
}

void onArriboNave(IdPlaneta idPlaneta, DatosArriboNave& msj) {
	logger << "Restan arribar " << msj._cantidadNavesFlota <<
			" naves de la flota " << logger.underline <<
			msj._idFlota << logger.nounderline << 
			" al planeta " << logger.underline <<
			idPlaneta << logger.nounderline << logger.endl;
	gFlotas->lock();
	if(gFlotas->contains(msj._idFlota.getId())) {
		Flota f = gFlotas->get(msj._idFlota.getId());
		f.setNavesEnVuelo(msj._cantidadNavesFlota);
		if(f.arribo()) {
			logger << "La flota " << logger.underline << msj._idFlota << 
					logger.nounderline << " del jugador local arribó "
					"completamente a destino" << logger.endl;
			gFlotas->remove(msj._idFlota.getId());
		} else {
			gFlotas->put(msj._idFlota.getId(), f);
		}
	}
	gFlotas->unlock();
}

void observarUniverso() {
	MiddlewareCliente& middleware = MiddlewareClienteSingleton::instance();
	uint8_t* buff = new uint8_t[MAX_DATOS];

	logger.set_pname("Jugador/ObservadorUniverso");
	logger << "Observador de universo lanzado!" << logger.endl;

	forever {
		if(middleware.recvMulticast(buff)) {
			MensajeAplicacion msj;
			msj.loadFromArray(buff);
			IdPlaneta origen;

			switch(msj.getEncabezado().getTipoMensaje()) {
			case AVISO_CREAR_FLOTA:
				onFlotaCreada((DatosCrearFlotaAvisoJugador&)msj.getDatos());
				break;
			case AVISO_CAMBIO_RUMBO_FLOTA:
				onCambioRumboFlota((DatosCambiarRumboAvisoJugador&)msj.getDatos());
				break;
			case CAMBIO_DUENO:
				msj.getEncabezado().getOrigen(origen);
				onCambioDuenio(origen, (DatosCambiaDuenio&)msj.getDatos());
				break;
			case CANT_NAVES:
				msj.getEncabezado().getOrigen(origen);
				onCantNaves(origen, (DatosCantidadNaves&)msj.getDatos());
				break;
			case ARRIBO_NAVE:
				msj.getEncabezado().getOrigen(origen);
				onArriboNave(origen, (DatosArriboNave&)msj.getDatos());
				break;
			default:
				logger << LogType::LOG_WARNING << "Mensaje desconocido! (ID=" <<
					msj.getEncabezado().getTipoMensaje() << logger.endl;
				break;
			}

			if(iniciado && juegoTerminado()) {
				delete [] buff;
				informarJuegoTerminado();
				kill(getppid(), SIGUSR1);
				exit(0);
			}

			if(iniciado && perdioJugadorLocal()) {
				delete [] buff;
				informarPerdioJugadorLocal();
				kill(getppid(), SIGUSR1);
				exit(0);
			}
		} else {
			logger << "Terminando observador de universo" << logger.endl;
			delete [] buff;
			exit(0);
		}
	}
}

pid_t lanzarObservadorUniverso(MapaFlotas* flotas, Jugador* jugador, IdUniverso idUniverso) {
	logger << "Lanzando observador de universo..." << logger.endl;

	pid_t obspid = fork();

	if(obspid==0) {
		Utiles::registrar_signal(SIGINT, SIG_DFL);
		gFlotas = flotas;
		gJugador = jugador;
		gPlanetas = RegistrySingleton::instance().getDirectorio().listarPlanetas(idUniverso);
		observarUniverso();
		exit(0);
	}

	return obspid;
}
